package com.jzo2o.orders.manager.service.impl;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.db.DbRuntimeException;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.api.customer.dto.response.AddressBookResDTO;
import com.jzo2o.api.foundations.ServeApi;
import com.jzo2o.api.foundations.dto.response.ServeAggregationResDTO;
import com.jzo2o.api.market.CouponApi;
import com.jzo2o.api.market.dto.request.CouponUseReqDTO;
import com.jzo2o.api.market.dto.response.AvailableCouponsResDTO;
import com.jzo2o.api.market.dto.response.CouponUseResDTO;
import com.jzo2o.api.trade.NativePayApi;
import com.jzo2o.api.trade.TradingApi;
import com.jzo2o.api.trade.dto.request.NativePayReqDTO;
import com.jzo2o.api.trade.dto.response.NativePayResDTO;
import com.jzo2o.api.trade.dto.response.TradingResDTO;
import com.jzo2o.api.trade.enums.PayChannelEnum;
import com.jzo2o.api.trade.enums.TradingStateEnum;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.common.model.msg.TradeStatusMsg;
import com.jzo2o.common.utils.BeanUtils;
import com.jzo2o.common.utils.DateUtils;
import com.jzo2o.common.utils.NumberUtils;
import com.jzo2o.common.utils.ObjectUtils;
import com.jzo2o.mvc.utils.UserContext;
import com.jzo2o.orders.base.config.OrderStateMachine;
import com.jzo2o.orders.base.constants.RedisConstants;
import com.jzo2o.orders.base.enums.OrderPayStatusEnum;
import com.jzo2o.orders.base.enums.OrderStatusChangeEventEnum;
import com.jzo2o.orders.base.enums.OrderStatusEnum;
import com.jzo2o.orders.base.mapper.OrdersMapper;
import com.jzo2o.orders.base.model.domain.Orders;
import com.jzo2o.orders.base.model.dto.OrderSnapshotDTO;
import com.jzo2o.orders.manager.model.dto.request.OrdersPayReqDTO;
import com.jzo2o.orders.manager.model.dto.request.PlaceOrderReqDTO;
import com.jzo2o.orders.manager.model.dto.response.OrdersPayResDTO;
import com.jzo2o.orders.manager.model.dto.response.PlaceOrderResDTO;
import com.jzo2o.orders.manager.porperties.TradeProperties;
import com.jzo2o.orders.manager.service.IOrdersCreateService;
import com.jzo2o.orders.manager.service.impl.client.CustomerClient;
import com.jzo2o.orders.manager.service.impl.client.MarketClient;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;

import static com.jzo2o.common.constants.ErrorInfo.Code.TRADE_FAILED;

/**
 * <p>
 * 下单服务类
 * </p>
 *
 * @author itcast
 * @since 2023-07-10
 */
@Slf4j
@Service
public class OrdersCreateServiceImpl extends ServiceImpl<OrdersMapper, Orders> implements IOrdersCreateService {

    @Resource
    private CustomerClient customerClient;

    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private ServeApi serveApi;

    //下面这里为啥要注入自己呢,,,因为我们想要获得对应的代理对象
    @Resource
    private OrdersCreateServiceImpl owner;

    @Resource
    private NativePayApi nativePayApi;
    @Resource
    private TradingApi tradingApi;

    //通过这个配置类,然后就可以拿到nacos中的配置文件,,比如拿到"jzo2o-orders-manager.yaml"配置文件中的商户号
    //可以跳进去这个配置类,看看里面是怎么定义的
    @Resource
    private TradeProperties tradeProperties;
    //注入订单状态机
    @Resource
    private OrderStateMachine orderStateMachine;

    @Resource
    private MarketClient marketClient;
    
    @Resource
    private CouponApi couponApi;
    
    
    
    /**
     * 生成订单id 格式：{yyMMdd}{13位id}
     *  这个订单的格式是年/月/日+订单序号
     * @return
     */
    private Long generateOrderId() {
        //调用redis自增一个序号
        Long increment = redisTemplate.opsForValue().increment(RedisConstants.Lock.ORDERS_SHARD_KEY_ID_GENERATOR);
        
        //生成订单号   2位年+2位月+2位日+13位序号
        long orderId = DateUtils.getFormatDate(LocalDateTime.now(), "yyMMdd") * 10000000000000L + increment;
        //上面这里生成订单号,是吧取出的时间格式,当做数字了,,老师说也可以把时间和13位序号都转换成string,然后字符串拼接到一起也可以
        return orderId;
    }


    /**
     * 下单
     * @param placeOrderReqDTO
     * @return
     */
    //代码优化,,把当前类中的事务管理@Transactional,在下面单独定义了一个add方法
    //@Transactional(rollbackFor = Exception.class)  
    @Override
    public PlaceOrderResDTO placeOrder(PlaceOrderReqDTO placeOrderReqDTO) {
        
        //下单人信息，获取地址簿，调用jzo2o-customer服务获取
            //这里也属于不同的数据库,所以也要远程调用,来查询地址簿信息
        AddressBookResDTO addressBookResDTO = customerClient.getDetail(placeOrderReqDTO.getAddressBookId());

        //服务相关信息,调用jzo2o-foundations获取
            //这里是不能直接通过注入mapper,调用人家jzo2o-foundations工程的数据库的,因为和当前项目是两个不同的数据库,
            //所以要涉及到多个不同微服务之间的调用,,跨服务调用
        //远程调用foundations服务查询服务相关的信息
        ServeAggregationResDTO serveAggregationResDTO = serveApi.findById(placeOrderReqDTO.getServeId());
        //准备组装数据
        Orders orders = new Orders();
        //下面封装的顺序,,完全是按照Orders表中的字段一个个set的,,,这样很清晰,不要怕麻烦
            //最开始苍穹外卖,就是这种方式,,自己总想用属性拷贝,代码简单,,但是看起来不够直观
            //老师说为啥,这里不建议直接属性拷贝;因为属性太多了,太杂了,一个不注意,可能就覆盖字段的值了
        //订单号
        Long orderId = generateOrderId();
        orders.setId(orderId);
        //下单人id,,这个就从线程那微信用户的id了,UserContext
        orders.setUserId(UserContext.currentUserId());
        //服务类型id
        orders.setServeTypeId(serveAggregationResDTO.getServeTypeId());
        //服务类型名称
        orders.setServeTypeName(serveAggregationResDTO.getServeTypeName());
        //服务项id
        orders.setServeItemId(serveAggregationResDTO.getServeItemId());
        //服务项的名称
        orders.setServeItemName(serveAggregationResDTO.getServeItemName());
        //服务项的图片
        orders.setServeItemImg(serveAggregationResDTO.getServeItemImg());
        //服务单位
        orders.setUnit(serveAggregationResDTO.getUnit());
        //服务id
        orders.setServeId(placeOrderReqDTO.getServeId());
        //订单状态,默认待支付
        orders.setOrdersStatus(OrderStatusEnum.NO_PAY.getStatus());
        //支付状态,默认未支付
        orders.setPayStatus(OrderPayStatusEnum.NO_PAY.getStatus());
        //单价
        orders.setPrice(serveAggregationResDTO.getPrice());
        //购买数量
        orders.setPurNum(placeOrderReqDTO.getPurNum());
        //总价
        BigDecimal totalAmount = serveAggregationResDTO.getPrice().multiply(new BigDecimal(placeOrderReqDTO.getPurNum()));
        orders.setTotalAmount(totalAmount);
        //优惠价格,,现在还没写优惠卷,所以先设置成0
            //下面销毁优惠卷,调用addWithCoupon这个方法的时候,,有获取到真正的优惠金额,然后计算出实际价格,,存到了数据库中
        orders.setDiscountAmount(BigDecimal.ZERO);
        //实际价格,总价格减去优惠价格
        orders.setRealPayAmount(NumberUtils.sub(orders.getTotalAmount(),orders.getDiscountAmount()));

        //city_code
        orders.setCityCode(serveAggregationResDTO.getCityCode());
        //服务地址
            //这里就要对拿到的地址簿,进行拼接详细的地址
        String serveAddress = new StringBuffer().append(addressBookResDTO.getProvince())
                .append(addressBookResDTO.getCity())
                .append(addressBookResDTO.getCounty())
                .append(addressBookResDTO.getAddress())
                .toString();
        orders.setServeAddress(serveAddress);
        //联系电话
        orders.setContactsPhone(addressBookResDTO.getPhone());
        //联系人姓名
        orders.setContactsName(addressBookResDTO.getName());
        //服务开始时间
        orders.setServeStartTime(placeOrderReqDTO.getServeStartTime());
        //经纬度
        orders.setLon(addressBookResDTO.getLon());
        orders.setLat(addressBookResDTO.getLat());
        //排序字段
        //排序字段,根据服务开始时间转为毫秒时间戳+订单后5位
        long sortBy = DateUtils.toEpochMilli(orders.getServeStartTime()) + orders.getId() % 100000;
        orders.setSortBy(sortBy);
        
        //下面的两大段注释代码,,可以先不看,直接看下面216行代码
        //代码优化,,把当前类中的事务管理@Transactional,在下面单独定义了一个add方法,
            //然后把涉及到数据库的,单独进行事务控制
            //要不涉及到远程服务调用,网络延迟问题等,高并发访问数据,会占用很多资源,一个事务就锁死一条资源,远程调用就得等待
        ////插入到数据库中
        //boolean save = this.save(orders);
        ////保存订单
        ////owner.add(orders);
        //if (!save) {
        //    throw new DbRuntimeException("下单失败");
        //}
        
        //保存订单
            //调用下面优化的add方法,,需要注意,应该是生成的代理对象,调用add方法才行;
            // 否则下面相当于本对象调用的add那就没法事务控制,即使add有@Transactional注解也不行;
            //因为首先我们要明白,开启本地事务,是生成的代理对象去执行的
        //this.add(orders);    //这个代码是错误示例
        //owner.add(orders);   //这个owner是我们注入的自己的对象,,,反正就是自己的代理对象
        //说实话,,,这里不是很理解,,为啥注入自己的对象,就是代理对象;或者理解成这个是类对象,而当前环境是对象实例,
            //那对象实例调用这个add方法,肯定就是本对象调用的;
            //而你注入的是类对象,本身就携带代理对象了,那让代理对象直接调用add,或者理解为代理对象的实例调用了这个方法,
            //算了,,反正不理解,会抄代码就行了,会解决问题.

        //判断如果使用了优惠券进行核销
        if(ObjectUtils.isNotNull(placeOrderReqDTO.getCouponId())){
            //进行核销
            owner.addWithCoupon(orders,placeOrderReqDTO.getCouponId());  //这里owner不懂看上面注释
        }else{
            //保存订单,这里加入了优惠卷销毁的逻辑,,所以上面207行的代码就注释掉了
            owner.add(orders);
        } 

        //返回数据
        PlaceOrderResDTO placeOrderResDTO = new PlaceOrderResDTO();
        placeOrderResDTO.setId(orders.getId());
        return placeOrderResDTO;
    }
    
    /**
     * 当下单时有优惠券调用此方法进行优惠券核销
     * 上面下单方法,,使用了优惠卷,就要销毁掉这个优惠卷,,上面用到了当前的方法addWithCoupon
     * 下面全局事务,用的seata分布式事务,可以回顾讲义_第八章秒杀抢购讲义_4.4.4优惠卷核销事务控制实现
     *      也就是说,,引入了seata依赖,,然后对要实现分布式事务的方法,开启全局事务,即标注@GlobalTransactional,就算完成了
     * @param orders
     * @param couponId
     */
    //@Transactional(rollbackFor = Exception.class)
    @GlobalTransactional//开启全局事务; 用的seata分布式事务
    public void addWithCoupon(Orders orders, Long couponId) {

        //远程调用优惠券服务进行核销
        CouponUseReqDTO couponUseReqDTO = new CouponUseReqDTO();
        //订单id
        couponUseReqDTO.setOrdersId(orders.getId());
        //优惠券id
        couponUseReqDTO.setId(couponId);
        //总金额
        couponUseReqDTO.setTotalAmount(orders.getTotalAmount());

        //核销成功返回优惠金额
            //老师说,跨服务调用,应该单独定个客户端,熔断降级,就上面第93行类似的MarketClient客户端;老师这里为了省事,就直接调的couponApi
        CouponUseResDTO couponUseResDTO = couponApi.use(couponUseReqDTO);
        //优惠金额
        orders.setDiscountAmount(couponUseResDTO.getDiscountAmount());
        //实际价格,总价格减去优惠价格
        orders.setRealPayAmount(NumberUtils.sub(orders.getTotalAmount(),orders.getDiscountAmount()));

        //保存订单信息
            //将上面优惠的金额,以及计算过后,,实际金额都存到数据库中
        add(orders);
        //模拟一个异常,,当发生异常的时候,当前数据库orders中数据能回滚,但是上面259行,调用的别的服务的数据库,就没法回滚,
            // 所以要用分布式事务,本地的事务就不能满足需求了
        int i=1/0;


    }
    

    /**
     * 将上面下单,插入到数据库中
     * 对上面placeOrder方法的优化补充,,,
     * 感觉没必要单独把方法写到接口中,直接写到serviceImpl中就可以了,
     *
     * @param orders
     */
    @Transactional(rollbackFor = Exception.class)
    //相当于下面被事务控制的只有一个插入本地的数据库,,就不涉及到远程服务的问题
        //自己理解:  这里还不算分布式事务,没有涉及到多个跨服务的数据库操作;因为这仍然是基于单个服务的本地事务管理,只是涉及到了跨服务查询
    @Override 
    public void add(Orders orders){
        //插入数据库----原本下面这段代码是上面placeOrder方法中封装好对象,插入数据库;这里是一种对本地事务涉及到跨服务问题的优化
        
        boolean save = this.save(orders);
        if(!save){
            throw new CommonException("下单失败");
        }
        //调用状态机的启动方法_day08_4对应视频的内容
        //参数列表：Long dbShardId 分库分表的时候要用, String bizId 订单id, T bizSnapshot 订单快照
        //封装快照对象,OrderSnapshotDTO
        //OrderSnapshotDTO orderSnapshotDTO = BeanUtils.toBean(orders, OrderSnapshotDTO.class);
        OrderSnapshotDTO orderSnapshotDTO = BeanUtils.toBean(baseMapper.selectById(orders.getId()), OrderSnapshotDTO.class);
        //老师在测试的时候,发现orders中的createTime为空,所以把上面代码注释掉,优化下,从数据库查一下就好了
            //好像是说,前面获取的orders还在事务中,所以没有最新对应的数据; 上面的selectById也可以用IService提供的getById
        
        //分库按用户id来分，用户id % 3 ;所以下面要把用户id传入(这里是"第七章 系统优化讲义,2.2.2,分库分表相关的内容")
            //下面状态机这里 都是字符串,后面转的字符串
        orderStateMachine.start(orders.getUserId(),orders.getId().toString(),orderSnapshotDTO);
    }

    /**
     * 订单支付
     * 这里其实只能算是生成二维码,真正支付,用户就直接对接微信支付接口了,就不用走我们订单管理服务了
     * @param id              订单id
     * @param ordersPayReqDTO 订单支付请求体
     * @return
     */
    @Override
    public OrdersPayResDTO pay(Long id, OrdersPayReqDTO ordersPayReqDTO) {
        //查一下订单
        Orders orders = getById(id);

        //如果订单不存在抛出异常
        if(ObjectUtils.isNull(orders)){
            throw new CommonException("订单不存在");
        }

        //如果订单已经支付成功直接返回
        Integer payStatus = orders.getPayStatus();//支付状态
        if(payStatus == OrderPayStatusEnum.PAY_SUCCESS.getStatus() && ObjectUtils.isNotEmpty(orders.getTradingOrderNo())){
            OrdersPayResDTO ordersPayResDTO = BeanUtils.toBean(orders,OrdersPayResDTO.class);
            ordersPayResDTO.setProductOrderNo(orders.getId());
            return ordersPayResDTO;

        }else{
            //请求支付服务生成二维码
                //把这段代码,单独抽取到generateQrCode方法中了,,可以看看定义;要不代码太多了,所以老师单独封装了一个方法
                //老师说代码是写着写着,然后感觉这个代码比较多,所以代码优化才单独抽出来一个私有方法,而不是从一开始就知道要抽取出来方法
            //下面生成二维码,要传入订单信息orders以及对应的渠道ordersPayReqDTO.getTradingChannel()
            NativePayResDTO nativePayResDTO = generateQrCode(orders, ordersPayReqDTO.getTradingChannel());
            
            //上面已经拿到二维码了,,剩下就是封装成OrdersPayResDTO然后返回
            OrdersPayResDTO ordersPayResDTO = BeanUtils.toBean(nativePayResDTO, OrdersPayResDTO.class);

            //更新订单表中的交易单号和支付渠道
            boolean update = lambdaUpdate()
                    .eq(Orders::getId, id)
                    .set(Orders::getTradingOrderNo, nativePayResDTO.getTradingOrderNo())
                    .set(Orders::getTradingChannel, nativePayResDTO.getTradingChannel())
                    .update();
            if(!update){
                log.info("请求支付服务生成二维码，更新订单:{}的交易单号和支付渠道失败",id);
                throw new CommonException("请求支付服务生成二维码，更新订单:"+id+"的交易单号和支付渠道失败");
            }

            return ordersPayResDTO;
        }
    }
    
    //补充方法: generateQrCode,上面pay调用到了,,,单独封装的
    /**
     * 请求支付服务,生成二维码
     * @param orders 订单信息
     * @param tradingChannel 请求的支付渠道
     * @return
     */
    private NativePayResDTO generateQrCode(Orders orders, PayChannelEnum tradingChannel) {

        //封装请求支付服务的参数
        NativePayReqDTO nativePayDTO = new NativePayReqDTO();

        //根据请求的支付渠道需要使用哪个商户号
        Long enterpriseId = ObjectUtils.equal(tradingChannel, PayChannelEnum.WECHAT_PAY) ? tradeProperties.getWechatEnterpriseId() : tradeProperties.getAliEnterpriseId();
        nativePayDTO.setEnterpriseId(enterpriseId);
        //家政的订单号
        nativePayDTO.setProductOrderNo(orders.getId());

        //金额,,这个拿的是实付金额
        nativePayDTO.setTradingAmount(orders.getRealPayAmount());
        //业务系统标识 ，家政订单服务请求支付服务统一指定jzo2o.orders
        nativePayDTO.setProductAppId("jzo2o.orders");
        //请求的支付渠道
        nativePayDTO.setTradingChannel(tradingChannel);
        //是否切换支付渠道
        //判断如果在订单中有支付渠道并且和新传入支付渠道不一致，说明用户切换支付渠道
        if(ObjectUtils.isNotEmpty(orders.getTradingChannel()) && ObjectUtils.notEqual(orders.getTradingChannel(),tradingChannel.getValue())){
            nativePayDTO.setChangeChannel(true);
        }
        //备注,服务 项名称
        nativePayDTO.setMemo(orders.getServeItemName());

        //请求支付服务生成二维码
            //这里就是远程调用,,调用trade服务(我们单独设计的支付服务模块),即调用里面的这个扫码支付的方法createDownLineTrading
        NativePayResDTO downLineTrading = nativePayApi.createDownLineTrading(nativePayDTO);

        if(ObjectUtils.isNotNull(downLineTrading)){
            return downLineTrading;
        }
        //如果执行了if条件,有return 就直接返回对应的结果了,,就不会再往下执行这个抛异常的代码;
            //只有不满足if条件,相当于else情况,才抛这个异常(也就是说不等于空,才说明申请成功)
        throw new CommonException("请求支付服务生成二维码失败");

    }

    /**
     * 请求支付服务查询支付结果
     * 如果代码有不理解的地方,,就跟着老师视频,一步一步分析debug代码的过程,前后端联调太麻烦了,要启动太多服务,,
     *  所以就直接跟着老师走,慢一点,一点点分析就好了,,
     *  主要明白有三个服务,一个是订单管理的有订单号; 对接的支付服务有"支付服务交易单号";最后是微信支付服务的(也可能是阿里),"第三方支付交易单号"
     *  还有订单的状态与支付服务不同的状态,,我弄混到一起就懵了,比如订单服务订单状态就有一个0:待支付,结果支付状态还有一个2:待支付,能不晕吗,太容易混到一起了;
     *      还有支付服务,,还有待支付的状态(不过这个状态和前面订单服务的支付状态数字就一样了,不过也没啥用,重点还是关注是哪个服务的,别弄混就好理解),,,
     *      唉,,所以最开始想跟着老师过一遍,,没咋认真分析各个表结构,,然后卡了一下午,感觉代码逻辑都不通,
     *      小笨蛋,都混合到一起了,,,代码能理清就怪了,这就说明了业务逻辑整体,包括表格的重要性,不懂业务结构,还有各个表的关系,那就费劲死;
     *      我想起来了,,,每次新的服务板块写代码,,这个表格理清关系都要花费大量的时间,,等理清表格关系,后面写代码就舒服多了
     *  这个各种状态信息,,可以打开对应的各种表看结构,包括封装的属性,,比如orders表,就能很清楚的看到都有什么,哪个字段都是干什么的
     * @param id 订单id
     * @return
     */
    @Override
    public OrdersPayResDTO getPayResultFromTradServer(Long id) {
        //查一下订单
        Orders orders = getById(id);
        //如果订单不存在抛出异常
        if(ObjectUtils.isNull(orders)){
            throw new CommonException("订单不存在");
        }
        //支付结果
        Integer payStatus = orders.getPayStatus();
        //如果未支付才去请求支付服务拿支付结果
            //这个NO_PAY未支付,,就指的是待支付的状态;  也就是未支付,且有交易单号,,那就说明调起二维码成功
            //如果订单支付成功,并且订单状态都修改过了,就不走这个if逻辑,直接把支付结果查出来就可以了
            //NO_PAY是订单管理的订单状态""2,未支付",,注意和下面支付服务的状态区分,太容易混了
        if(payStatus==OrderPayStatusEnum.NO_PAY.getStatus() && ObjectUtils.isNotEmpty(orders.getTradingOrderNo())){
            //拿到交易单号
            Long tradingOrderNo = orders.getTradingOrderNo();

            //根据交易单号请求支付服务的查询支付结果接口
                //这里就是调用支付服务,然后支付服务去调用微信支付的接口,根据交易单号返回对应的交易单数据
            TradingResDTO tradResultByTradingOrderNo = tradingApi.findTradResultByTradingOrderNo(tradingOrderNo);
            //如果支付成功，更新订单表的数据库(看视频跟着老师debug代码,才好理解)
                //1.说明这里有两种情况,支付成功;
                // 还有一种那就是未支付成功(比如弹出二维码,但是用户取消支付了),那这种情况就跳过这个if
                    //当我们跳过这个if,会发现后面就没有处理这种情况的代码,一直没有retrun,那就会导致,一直执行完if语句,到378行左右,会再次执行相应的逻辑
                //2.YJS是支付服务的状态,(4, "已结算（已付款）"的状态)
                //3. 我看视频的时候,如果支付失败,没有第三方支付的交易号; 只有订单服务的订单id和支付服务交易单号(支付服务的)
            if(ObjectUtils.equal(tradResultByTradingOrderNo.getTradingState(), TradingStateEnum.YJS)){
                TradeStatusMsg tradeStatusMsg = new TradeStatusMsg();
                //老师说,如果不确定,字段太多,还是一个个写吧,别弄差了;就没属性拷贝,属性拷贝的前提是属性名都一一对应才行
                //订单id
                tradeStatusMsg.setProductOrderNo(id);
                //支付渠道
                tradeStatusMsg.setTradingChannel(tradResultByTradingOrderNo.getTradingChannel());
                //交易单号
                tradeStatusMsg.setTradingOrderNo(tradResultByTradingOrderNo.getTradingOrderNo());
                //第三方（微信）的交易单号
                tradeStatusMsg.setTransactionId(tradResultByTradingOrderNo.getTransactionId());
                //支付成功code
                    //这个状态是订单状态,,我总和支付服务的状态混到一块
                tradeStatusMsg.setStatusCode(OrderPayStatusEnum.PAY_SUCCESS.getStatus());
                //支付成功名称
                tradeStatusMsg.setStatusName(OrderPayStatusEnum.PAY_SUCCESS.name());
                //更新数据库
                    //这个owner相当于是注入自己,是一个代理对象,上面有个方法讲过
                owner.paySuccess(tradeStatusMsg);
                //构造返回的数据
                OrdersPayResDTO ordersPayResDTO = BeanUtils.toBean(tradeStatusMsg,OrdersPayResDTO.class);
                //支付成功
                ordersPayResDTO.setPayStatus(OrderPayStatusEnum.PAY_SUCCESS.getStatus());
                return ordersPayResDTO;
            }
        }
        //如果支付成功,且订单表状态已经修改过,那直接返回结果就行;  
        //如果未支付成功,但是支付服务交易单为空,说明二维码都没有生成,没有交易单; 那也是直接查询订单表就可以了
        //如果未支付成功,支付服务交易单不为空的情况下,即生成了二维码,微信服务返回了交易订单,但是上面if代码,虽然进入了if,并没有单独处理这种情况,
            //导致一直没有return,那也就意味着,等执行完if语句,还会继续往下执行,就会走到当前这里,然后继续执行;因为一直没有return,就只能继续执行
        //需要注意,,,在跟着老师视频,debug代码的时候,看到接收的参数,如果支付失败,有支付服务交易号,但是是没有第三方支付的交易号的,,就是微信支付的交易号没有
        OrdersPayResDTO ordersPayResDTO = BeanUtils.toBean(orders,OrdersPayResDTO.class);
        ordersPayResDTO.setProductOrderNo(orders.getId());
        return ordersPayResDTO;
    }

    /**
     * 支付成功， 更新数据库的订单表及其他信息
     * 看上面getPayResultFromTradServer方法,也能看出来,整个逻辑,其实只有支付成功才更新对应的数据库订单表,,
     *     其余的直接查询订单表,返回相应信息,都没有请求微信支付返回数据,,因为不支付成功也不会有第三方支付的交易号,,,想查也查不了
     * sql：update orders set pay_status=? ... where id=? and  orders_status=0
     * @param tradeStatusMsg 交易状态消息
     */
    @Override
    @Transactional
    public void paySuccess(TradeStatusMsg tradeStatusMsg) {
        //补充一些校验代码;;;老师后面状态机优化代码,也没有下面的校验代码,,我就也注释掉了
        //查询订单
        //Orders orders = baseMapper.selectById(tradeStatusMsg.getProductOrderNo());
        //if (ObjectUtil.isNull(orders)) {
        //    throw new CommonException(TRADE_FAILED, "订单不存在");
        //}
        ////校验支付状态,如果不是待支付状态则不作处理(即只有是待支付状态,才跳过这个if条件,继续执行)
        //if (ObjectUtil.notEqual(OrderPayStatusEnum.NO_PAY.getStatus(), orders.getPayStatus())) {
        //    log.info("更新订单支付成功，当前订单:{}支付状态不是待支付状态", orders.getId());
        //    return;
        //}
        ////校验订单状态,如果不是待支付状态则不作处理
        //if (ObjectUtils.notEqual(OrderStatusEnum.NO_PAY.getStatus(),orders.getOrdersStatus())) {
        //    log.info("更新订单支付成功，当前订单:{}状态不是待支付状态", orders.getId());
        //}
        ////第三方支付单号校验
        //if (ObjectUtil.isEmpty(tradeStatusMsg.getTransactionId())) {
        //    throw new CommonException("支付成功通知缺少第三方支付单号");
        //}


        //支付成功更新订单表
        //boolean update = lambdaUpdate()
        //        .eq(Orders::getId, tradeStatusMsg.getProductOrderNo())//订单id
        //        .eq(Orders::getOrdersStatus,0)//订单状态只能由待支付变为派单中;别的状态,比如200:待服务,你再更新成派单中,不合适吧
        //        .set(Orders::getPayStatus, OrderPayStatusEnum.PAY_SUCCESS.getStatus())//更新为支付成功
        //        .set(Orders::getTransactionId, tradeStatusMsg.getTransactionId())
        //        .set(Orders::getOrdersStatus, OrderStatusEnum.DISPATCHING.getStatus())//订单状态变为派单中
        //        .set(Orders::getPayTime, LocalDateTime.now())
        //        .update();
        ////如果更新失败,抛异常信息
        //if(!update){
        //    throw new CommonException("支付成功更新订单:"+tradeStatusMsg.getProductOrderNo()+"状态为派单中失败");
        //}
        
        //后面用状态机来管理状态,,所以上面注释的代码,就优化掉
        //订单,,,因为下面要用到用户id(475行),所以查了一下orders表,别的地方好像也没用到里面的参数了
            //传入的参数: tradeStatusMsg.getProductOrderNo()就是订单id
        Orders orders = getById(tradeStatusMsg.getProductOrderNo());  

        //使用状态机将待支付状态改为派单中
            //封装快照对象OrderSnapshotDTO
        //Long dbShardId, String bizId, StatusChangeEvent statusChangeEventEnum, T bizSnapshot
        OrderSnapshotDTO orderSnapshotDTO = new OrderSnapshotDTO();
        orderSnapshotDTO.setTradingOrderNo(tradeStatusMsg.getTradingOrderNo());//交易单号
        orderSnapshotDTO.setTradingChannel(tradeStatusMsg.getTradingChannel());//支付通道
        orderSnapshotDTO.setPayTime(LocalDateTime.now());//支付成功时间
        orderSnapshotDTO.setThirdOrderId(tradeStatusMsg.getTransactionId());//第三方支付单号
        
        //执行事件
            //注意事件具体的实现逻辑,要在对应的动作类中写,,下面的动作类是OrderPayedHandler.java
            //怎么找到的这个动作类呢,怎么关联的,动作类中指定了容器中bean的名称,规则是：状态机名称_事件名称,所以绑定了对应事件
        orderStateMachine.changeStatus(orders.getUserId(),tradeStatusMsg.getProductOrderNo().toString(), 
                OrderStatusChangeEventEnum.PAYED,orderSnapshotDTO);   //这里是四个参数,我换了下行,要不看着不舒服
        //上面的是事件的触发,那我们除了要更改状态机表中状态,也要更改我们订单表对应的状态,,,就是上面update语句注释掉了;
            //因此,,我们要去对应事件执行的动作类去触发,就是OrderPayedHandler.java这个类,,,感觉 这个状态机好麻烦呀,
            //要新增加好多东西,然后还要更改原来代码,好麻烦
    }

    /**
     * 查询支付超时订单id列表
     * @param count 数量
     * @return
     */
    @Override
    public List<Orders> queryOverTimePayOrdersListByCount(Integer count) {
        //sql：select * from orders where orders_status = 0 and create_time < 当前时间-15分钟 limit 100
            //这个limit是分页查询的参数,比如`LIMIT 100;`   表示每页显示100条数据
        List<Orders> list = lambdaQuery()
                .eq(Orders::getOrdersStatus, OrderStatusEnum.NO_PAY.getStatus())
                .lt(Orders::getCreateTime, LocalDateTime.now().minusMinutes(15))
                .last("limit " + count)
                .list();
        
        return list;
    }


    /**
     * 获取可用优惠券
     *
     * @param serveId 服务id
     * @param purNum  购买数量
     * @return 可用优惠券列表
     */
    @Override
    public List<AvailableCouponsResDTO> getAvailableCoupons(Long serveId, Integer purNum) {
        //远程调用foundations
        ServeAggregationResDTO serveAggregationResDTO = serveApi.findById(serveId);

        if(ObjectUtils.isNull(serveAggregationResDTO) || serveAggregationResDTO.getSaleStatus() != 2){
            throw new CommonException("服务信息不可用");
        }
        //单价
        BigDecimal price = serveAggregationResDTO.getPrice();

        //计算订单总价
        BigDecimal totalAmount = price.multiply(new BigDecimal(purNum));

        //远程调用优惠券服务查询可用优惠券
        List<AvailableCouponsResDTO> available = marketClient.getAvailable(totalAmount);

        //返回可用优惠券列表

        return available;
    }
}
